package core

import (
	"animal/octopus/cache"
	"animal/octopus/common"
	"animal/octopus/orm"
	"regexp"
	"strings"
	"time"

	log "github.com/sirupsen/logrus"
	"gorm.io/gorm"
)

// ------------------------------------------------------------------------------
//
// ------------------------------------------------------------------------------
func CreateModule(db *gorm.DB, param common.CreateModuleParam) (uint, error) {
	var moduleID uint

	// check parent
	if param.Parent != 0 {
		module, err := cache.Instance().FindModuleByID(param.Parent)
		if err != nil {
			log.WithField("parent", param.Parent).Error("cannot find parent")
			return 0, err
		}

		if module.ModuleType != "system" {
			log.Error("report module cannot create sub-module")
			return 0, common.NewErrCode(common.ErrGeneralError)
		}
	}

	if err := common.DB(db).Transaction(func(tx *gorm.DB) error {
		// create module in database
		module, err := orm.CreateModule(tx, param)
		if err != nil {
			log.WithField("param", common.MustMarshal(param)).
				Error("create module in database failed")
			return err
		}

		// create module in cache
		if err := cache.Instance().CreateModule(module); err != nil {
			log.WithField("param", common.MustMarshal(param)).
				Error("create module in cache failed")
			return err
		}

		// one module one topo view
		if _, err := CreateTopoView(tx, common.CreateTopoViewParam{
			Name:     param.Name + "-topo-view",
			Desc:     "auto create topo view",
			ModuleID: module.ID,
		}); err != nil {
			log.Error("create topo view failed")
			return err
		}

		moduleID = module.ID
		return nil
	}); err != nil {
		log.WithField("param", common.MustMarshal(param)).
			Error("create module failed")
		return 0, err
	}

	return moduleID, nil
}

// ------------------------------------------------------------------------------
//
// ------------------------------------------------------------------------------
func EditModule(db *gorm.DB, param common.EditModuleParam) error {
	if err := common.DB(db).Transaction(func(tx *gorm.DB) error {
		if err := orm.EditModule(tx, param); err != nil {
			log.WithField("ID", param.ID).Error("edit module in database failed")
			return err
		}
		if err := cache.Instance().EditModule(param); err != nil {
			log.WithField("ID", param.ID).Error("edit module in cache failed")
			return err
		}
		return nil
	}); err != nil {
		log.WithField("ID", param.ID).Error("edit module failed")
		return err
	}

	return nil
}

// ------------------------------------------------------------------------------
//
// ------------------------------------------------------------------------------
func BatchEditModule(db *gorm.DB, param common.BatchEditModuleParam) error {
	if param.Parent == 0 {
		log.Error("cannot batch set root module")
		return common.NewErrCode(common.ErrGeneralError)
	}

	for _, moduleID := range param.Modules {
		module, err := cache.Instance().FindModuleByID(moduleID)
		if err != nil {
			log.WithField("ID", moduleID).Error("cannot find module")
			return common.NewErrCode(common.ErrGeneralError)
		}

		if err := EditModule(db, common.EditModuleParam{
			ID:     module.ID,
			Name:   module.Name,
			Desc:   module.Desc,
			Parent: param.Parent,
		}); err != nil {
			log.WithField("ID", module.ID).Error("edit module failed")
			return err
		}
	}

	return nil
}

// ------------------------------------------------------------------------------
//
// ------------------------------------------------------------------------------
func DeleteModule(db *gorm.DB, param common.DeleteModuleParam) error {
	// check module existence
	module, err := cache.Instance().FindModuleByID(param.ID)
	if err != nil {
		log.WithField("ID", param.ID).Error("cannot find module to delete")
		return err
	}

	// check sub-module existence
	if len(module.Children) > 0 {
		log.WithFields(log.Fields{
			"ID":   param.ID,
			"size": len(module.Children),
		}).Error("the module which has children cannot be deleted")
		return common.NewErrCode(common.ErrGeneralError)
	}

	if err := common.DB(db).Transaction(func(tx *gorm.DB) error {
		if err := orm.DeleteModule(tx, param); err != nil {
			log.WithField("ID", param.ID).Error("delete module from database failed")
			return err
		}
		if err := cache.Instance().DeleteModule(module); err != nil {
			log.WithField("ID", param.ID).Error("delete module from cache failed")
			return err
		}
		return nil
	}); err != nil {
		log.WithField("ID", param.ID).Error("delete module failed")
		return err
	}

	return nil
}

// ------------------------------------------------------------------------------
//
// ------------------------------------------------------------------------------
func GetChildren(parent uint) ([]common.ModuleInfo, error) {
	return cache.Instance().GetChildren(parent)
}

// ------------------------------------------------------------------------------
//
// ------------------------------------------------------------------------------
func GetDescendants(root uint) (*common.GetDescendantsResult, error) {
	return cache.Instance().GetDescendants(root)
}

// ------------------------------------------------------------------------------
//
// ------------------------------------------------------------------------------
func Report(db *gorm.DB, info common.ReportInfo) error {
	if err := common.DB(db).Transaction(func(tx *gorm.DB) error {
		module, err := findReportModule(tx, info)
		if err != nil {
			log.Error("find report module failed")
			return err
		}

		if err := checkReportParam(module, info); err != nil {
			log.Error("check report param failed")
			return err
		}

		// update module in database
		if err := orm.UpdateModule(tx, module.ID); err != nil {
			log.Error("update module in database failed")
			return err
		}

		// update module in cache
		module.UpdateAt = time.Now()
		if !module.Active {
			cache.AddEvent[cache.CModuleCore]("module active", module.ID,
				cache.ToModuleCore(module))
		}
		module.Active = true

		return processDependencies(tx, module, info)
	}); err != nil {
		log.WithField("info", info).Error("report failed")
		return err
	}

	log.WithField("info", common.MustMarshal(info)).Info("report success")

	return nil
}

// ------------------------------------------------------------------------------
//
// ------------------------------------------------------------------------------
func ImportModules(params []common.ImportModuleParam) error {
	var nameSet common.Set = make(common.Set)
	nameSet.Add("none")

	var firstRecord bool = true
	var app string
	var space string

	for _, param := range params {
		if firstRecord {
			if param.Parent != "none" {
				log.Error("the first module's parent is not 'none'")
				return common.NewErrCode(common.ErrGeneralError)
			}

			parts := strings.Split(param.Name, ".")
			if len(parts) != 2 {
				log.Error("the first module's name is invalid")
				return common.NewErrCode(common.ErrGeneralError)
			}

			app = parts[0]
			space = parts[1]
			firstRecord = false
		}

		if !nameSet.Contains(param.Parent) {
			log.WithField("param", common.MustMarshal(param)).
				Error("cannot find parent in import data")
			return common.NewErrCode(common.ErrGeneralError)
		}

		if nameSet.Contains(param.Name) {
			log.WithField("param", common.MustMarshal(param)).
				Error("module name conflicts in import data")
			return common.NewErrCode(common.ErrGeneralError)
		}
		nameSet.Add(param.Name)

		// check module name existence
		_, err := cache.Instance().FindModuleByName(param.Name)
		if err == nil {
			log.WithField("param", common.MustMarshal(param)).
				Error("module name already exists")
			return common.NewErrCode(common.ErrGeneralError)
		}
	}

	if err := common.DB(nil).Transaction(func(tx *gorm.DB) error {
		for _, param := range params {
			var parentID uint = 0
			if param.Parent != "none" {
				module, err := cache.Instance().FindModuleByName(param.Parent)
				if err != nil {
					log.WithField("param", common.MustMarshal(param)).
						Error("cannot find parent in database")
					return common.NewErrCode(common.ErrGeneralError)
				}
				parentID = module.ID
			}

			if moduleID, err := CreateModule(tx, common.CreateModuleParam{
				Name:       param.Name,
				Desc:       param.Desc,
				Parent:     parentID,
				CreateType: "hand",
				ModuleType: "system",
				App:        app,
				Space:      space,
			}); err != nil {
				log.WithField("param", common.MustMarshal(param)).
					Error("create module failed")
				return common.NewErrCode(common.ErrGeneralError)
			} else {
				log.WithFields(log.Fields{
					"param": common.MustMarshal(param),
					"ID":    moduleID,
				}).Info("create imported module success")
			}
		}
		return nil
	}); err != nil {
		log.Error("import data failed")
		return err
	}

	return nil
}

/////////////////////////////////////////////////////////////////////////////////

// ------------------------------------------------------------------------------
//
// ------------------------------------------------------------------------------
func processDependencies(db *gorm.DB, module *cache.CModule,
	info common.ReportInfo) error {

	addDeps, udtDeps, delDeps := compareDependencies(module.Dependencies,
		info.Dependencies)

	if err := addDependencies(db, module.ID, addDeps); err != nil {
		log.Error("add dependencies failed")
		return err
	}

	if err := updateDependencies(db, module.ID, udtDeps); err != nil {
		log.Error("add dependencies failed")
		return err
	}

	if err := deleteDependencies(db, module.ID, delDeps); err != nil {
		log.Error("add dependencies failed")
		return err
	}

	return nil
}

// ------------------------------------------------------------------------------
//
// ------------------------------------------------------------------------------
func findReportModule(db *gorm.DB, info common.ReportInfo) (*cache.CModule, error) {
	var rootModuleID uint

	// find root module
	var rootModName string = info.App + "." + info.Space
	if root, err := cache.Instance().FindRootModuleByName(rootModName); err != nil {
		log.Info("cannot find root module, then create it")
		if moduleID, err := CreateModule(db, common.CreateModuleParam{
			Name:       rootModName,
			Desc:       "auto created root module",
			CreateType: "auto",
			ModuleType: "system",
			Parent:     0,
			App:        info.App,
			Space:      info.Space,
		}); err != nil {
			log.WithFields(log.Fields{"info": info, "error": err}).
				Error("create module failed")
			return nil, err
		} else {
			rootModuleID = moduleID
		}
	} else {
		rootModuleID = root.ID
	}

	// find report module
	module, err := cache.Instance().FindModuleByName(info.Instance)
	if err != nil { // create report module mounted under root module
		rules, err := orm.GetReportRuleList(nil, rootModuleID)
		if err != nil {
			log.WithField("module", rootModuleID).Error("get report rule list failed")
			return nil, err
		}

		log.WithField("size", len(rules)).Info("find report rule")

		if _, err := CreateModule(db, common.CreateModuleParam{
			Name:       info.Instance,
			Desc:       "report module",
			CreateType: "auto",
			ModuleType: info.Type,
			Parent:     tryMatchReportParent(info, rules, rootModuleID),
			App:        info.App,
			Space:      info.Space,
		}); err != nil {
			log.WithFields(log.Fields{"info": info, "error": err}).
				Error("create report module failed")
			return module, err
		} else {
			module, err = cache.Instance().FindModuleByName(info.Instance)
			if err != nil {
				log.WithField("name", info.Instance).Error("cannot find module")
				return module, err
			}
		}
	}

	return module, nil
}

// ------------------------------------------------------------------------------
//
// ------------------------------------------------------------------------------
func tryMatchReportParent(info common.ReportInfo, rules []common.ReportRuleInfo,
	rootModuleID uint) uint {
	for _, rule := range rules {
		var matchString string
		if rule.MatchType == "type" {
			matchString = info.Type
		} else if rule.MatchType == "name" {
			matchString = info.Instance
		} else {
			log.WithField("matchType", rule.MatchType).Error("invalid match type")
			return rootModuleID
		}

		matched, err := regexp.MatchString(rule.MatchRule, matchString)
		if err != nil {
			log.WithFields(log.Fields{
				"string":  matchString,
				"pattern": rule.MatchRule,
				"error":   err,
			}).Error("match failed")
		}

		if matched {
			log.WithFields(log.Fields{
				"string":     matchString,
				"pattern":    rule.MatchRule,
				"parentId":   rule.ParentId,
				"parentName": rule.ParentName,
			}).Info("match success")
			return rule.ParentId
		} else {
			log.WithFields(log.Fields{
				"string":  matchString,
				"pattern": rule.MatchRule,
			}).Info("not matched")
		}
	}

	return rootModuleID
}

// ------------------------------------------------------------------------------
//
// ------------------------------------------------------------------------------
func checkReportParam(module *cache.CModule, info common.ReportInfo) error {
	if module.App != info.App {
		log.WithFields(log.Fields{
			"module": module,
			"report": common.MustMarshal(info)}).
			Error("app mismatch")
		return common.NewErrCode(common.ErrGeneralError)
	}

	if module.Space != info.Space {
		log.WithFields(log.Fields{
			"module": module,
			"report": common.MustMarshal(info)}).
			Error("space mismatch")
		return common.NewErrCode(common.ErrGeneralError)
	}

	if module.ModuleType != info.Type {
		log.WithFields(log.Fields{
			"module": module,
			"report": common.MustMarshal(info)}).
			Warn("module type changed")
	}

	return nil
}

// ------------------------------------------------------------------------------
//
// ------------------------------------------------------------------------------
func compareDependencies(oldDeps []*cache.CDependency, newDeps []common.DependencyInfo) (
	[]common.DependencyInfo, []UpdateDepData, []*cache.CDependency) {
	var addDeps []common.DependencyInfo
	var udtDeps []UpdateDepData
	var delDeps []*cache.CDependency

	// mark to be added and to be updated
	for _, newDep := range newDeps {
		var found bool = false
		for _, oldDep := range oldDeps {
			if newDep.TargetType == oldDep.TargetType &&
				newDep.TargetID == oldDep.TargetID {
				udtDeps = append(udtDeps, UpdateDepData{
					OldDep: oldDep,
					NewDep: newDep,
				})
				found = true
				break
			}
		}
		if !found {
			addDeps = append(addDeps, newDep)
		}
	}

	// mark to be deleted
	for _, oldDep := range oldDeps {
		var found bool = false
		for _, newDep := range newDeps {
			if newDep.TargetType == oldDep.TargetType &&
				newDep.TargetID == oldDep.TargetID {
				found = true
				break
			}
		}
		if !found { // remove from old dependencies
			delDeps = append(delDeps, oldDep)
		}
	}

	return addDeps, udtDeps, delDeps
}

// ------------------------------------------------------------------------------
//
// ------------------------------------------------------------------------------
func addDependencies(db *gorm.DB, moduleID uint, addDeps []common.DependencyInfo) error {
	for _, dep := range addDeps {
		if err := CreateDependency(db, moduleID, dep); err != nil {
			log.Error("create dependency failed")
			return err
		}
	}
	return nil
}

// ------------------------------------------------------------------------------
//
// ------------------------------------------------------------------------------
func updateDependencies(db *gorm.DB, moduleID uint, udtDeps []UpdateDepData) error {
	for _, dep := range udtDeps {
		if err := UpdateDependency(db, moduleID, dep.OldDep.ID, dep.NewDep); err != nil {
			log.WithFields(log.Fields{
				"module":     moduleID,
				"dependency": dep.OldDep.ID,
			}).Error("update dependency failed")
			return err
		}
	}
	return nil
}

// ------------------------------------------------------------------------------
//
// ------------------------------------------------------------------------------
func deleteDependencies(db *gorm.DB, moduleID uint, delDeps []*cache.CDependency) error {
	for _, dep := range delDeps {
		if err := DeleteDependency(db, moduleID, dep.ID); err != nil {
			log.WithFields(log.Fields{
				"module":     moduleID,
				"dependency": common.MustMarshal(dep),
			}).Error("delete dependency failed")
			return err
		}
	}
	return nil
}
